package br.cin.ufpe.algorithms.kmeans;

import java.io.IOException;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;

import br.cin.ufpe.basic.IntervalAttribute;
import br.cin.ufpe.basic.SCluster;
import br.cin.ufpe.basic.SymbolicPattern;

public class SymbolicFuzzyKMeans {

	private int numAttributes;
	private double fuzzinessCoef;
	private List<SymbolicPattern> trainingSet;
	private List<SCluster> clusters;
	private double[][] membershipMatrix;
	private double[][] currentMembershipMatrix;
	private double threshold;
	private double scaleParameter;
	private double[] outlierMembershipMatrix;
	private int iteracoes;

	public SymbolicFuzzyKMeans( int qClusters,double fuzziness, double threshold) {
		this.trainingSet = new ArrayList<SymbolicPattern>();
		this.fuzzinessCoef = fuzziness;
		this.clusters = new ArrayList<SCluster>(qClusters);
		this.threshold = threshold;
		for (int i = 0; i < qClusters; i++) {
			clusters.add(new SCluster());
		}
	}

	public double getL2Distance(SymbolicPattern p1, SymbolicPattern p2) {
		double temp = 0.0;
		for (int j = 0; j < numAttributes; j++) {
			IntervalAttribute intAtt1 = p1.getAttributes().get(j);
			IntervalAttribute intAtt2 = p2.getAttributes().get(j);
			double factor1 = Math.pow((intAtt1.getCenter()- intAtt2.getCenter()), 2);
			double factor2 = Math.pow((intAtt1.getRadius()- intAtt2.getRadius()), 2);
			temp = temp + trunc(factor1 + factor2);
		}
		return temp;
	}

	private void computeMembershipDegree(int i, int q) {
		SymbolicPattern pattern = trainingSet.get(i);
		SCluster sCluster = clusters.get(q);
		double num1 = getL2Distance(pattern, sCluster.getPrototype());
		double num2 = 0.0;
		double factor1 = 0.0;
		double factor2 = 0.0;
		double exp = 1 / (fuzzinessCoef - 1);
		for (SCluster cluster : clusters) {
			num2 = getL2Distance(pattern, cluster.getPrototype());
			factor1 = factor1 + (num1/num2);
		}
		factor1 = Math.pow((factor1), exp);
		num2 = Math.pow((scaleParameter*4), 2);
		factor2 = num1 / num2;
		factor2 = Math.pow((factor2), exp);
		double result = (1 / (factor1 + factor2));
		this.currentMembershipMatrix[i][q] = trunc(result);
	}

	private void computeScale() {
		double result = 0.0;
		double sumCj = 0.0;
		double sumRj = 0.0;
		int n = trainingSet.size();
		for (int i = 0; i < n; i++) {
			SymbolicPattern p1 = trainingSet.get(i);
			for (int j = 0; j < p1.getAttributes().size(); j++) {
				IntervalAttribute att = p1.getAttributes().get(j);
				sumCj = sumCj + att.getCenter();
				sumRj = sumRj + att.getRadius();
			}
		}
		double den = n* numAttributes;
		double fator1 = sumCj / den;
		double fator2 = sumRj / den;
	    fator1 = Math.sqrt(fator1);
		fator2 = Math.sqrt(fator2);
		result = fator1 + fator2;
		this.scaleParameter = result;
	}

	public void setFuzziness(double m) {
		this.fuzzinessCoef = m;
	}

	private void computePrototype(int q) {
		SymbolicPattern prototype = new SymbolicPattern(numAttributes);
		double temp = 0.0;
		double denominator = 0.0;
		for (int i = 0; i < trainingSet.size(); i++) {
			SymbolicPattern pattern = trainingSet.get(i);
			temp = membershipMatrix[i][q];
			temp = Math.pow(temp, fuzzinessCoef);
			pattern = product(pattern, temp);
			prototype = sumAttributes(prototype, pattern);
			denominator = denominator + temp;
		}
		prototype = product(prototype, (1/denominator));
		clusters.get(q).setPrototype(prototype);
	}

	private SymbolicPattern sumAttributes(SymbolicPattern centerPrototype,SymbolicPattern pattern) {
		SymbolicPattern returnPattern = new SymbolicPattern(numAttributes);
		for (int i = 0; i < centerPrototype.getAttributes().size(); i++) {
			IntervalAttribute att = centerPrototype.getAttributes().get(i);
			IntervalAttribute retAtt = returnPattern.getAttributes().get(i);
			retAtt.setCenter(att.getCenter()+ pattern.getAttributes().get(i).getCenter());
			retAtt.setRadius(att.getRadius()+ pattern.getAttributes().get(i).getRadius());
		}
		return returnPattern;
	}

	private SymbolicPattern product(SymbolicPattern p, double temp) {
		SymbolicPattern pattern = new SymbolicPattern(this.numAttributes);
		for (int i = 0; i < p.getAttributes().size(); i++) {
			IntervalAttribute att = p.getAttributes().get(i);
			IntervalAttribute retAtt = pattern.getAttributes().get(i);
			retAtt.setCenter(trunc(att.getCenter() * temp));
			retAtt.setRadius(trunc(att.getRadius() * temp));
		}
		return pattern;
	}

	public void process() throws IOException {
		initMembershipMatrix();
		initializePrototypes();
		computeScale();
		
		List<SymbolicPattern> dataSet = getTrainingSet();
		
		do {
			for (int q = 0; q < clusters.size(); q++) {
				computePrototype(q);
			}
			for (int q = 0; q < clusters.size(); q++) {
				for (int i = 0; i < dataSet.size(); i++) {
					computeMembershipDegree(i, q);
				}
			}
			for(int i =0; i< dataSet.size();i++){
				computeOutlierMembership(i);
			}
			iteracoes++;
		} while (compareMembershipDegrees());
	}

	private void computeOutlierMembership(int i) {
		double result = 0.0;
		for(int q = 0; q<clusters.size();q++){
			result = result + currentMembershipMatrix[i][q];
		}
		outlierMembershipMatrix[i] = trunc(1- result);
	}

	private boolean compareMembershipDegrees() {
		boolean iteract = true;
		double current = 0.0;
		double previous = 0.0;
		List<Double> dif = new ArrayList<Double>();
		for (int i = 0; i < trainingSet.size(); i++) {
			for (int q = 0; q < clusters.size(); q++) {
				double v1 = currentMembershipMatrix[i][q];
				double v2 = membershipMatrix[i][q];
				current = current + v1;
				previous = previous + v2;
			}
			double result = current - previous;
			dif.add(result);
		}
		double maxDifference = -999999999;
		for(Double res : dif){
			if(res>maxDifference){
				maxDifference = res;
			}
		}
		if (Math.abs(trunc(maxDifference)) < threshold) {
			iteract = false;
		} else {
			for (int i = 0; i < trainingSet.size(); i++) {
				for (int q = 0; q < clusters.size(); q++) {
					membershipMatrix[i][q] = currentMembershipMatrix[i][q];
				}
			}
		}
		return iteract;
	}

	private void initMembershipMatrix() {
		setMembershipMatrix(new double[trainingSet.size()][clusters.size()]);
		for (int i = 0; i < trainingSet.size(); i++) {
			for (int q = 0; q < clusters.size(); q++) {
				membershipMatrix[i][q] = trunc(1 / clusters.size()+ Math.random());
			}
		}
		setCurrentMembershipMatrix(new double[trainingSet.size()][clusters.size()]);
		setOutlierMembershipMatrix(new double[trainingSet.size()]);
	}

	private void initializePrototypes() {
		for (int i = 0; i < clusters.size(); i++) {
			clusters.get(i).setPrototype(new SymbolicPattern(numAttributes));
		}
	}

	public void setMembershipMatrix(double[][] membershipMatrix) {
		this.membershipMatrix = membershipMatrix;
	}

	public double[][] getMembershipMatrix() {
		return membershipMatrix;
	}

	public void setCurrentMembershipMatrix(double[][] membershipMatrix) {
		this.currentMembershipMatrix = membershipMatrix;
	}

	public double[][] getCurrentMembershipMatrix() {
		return currentMembershipMatrix;
	}

	public void setTrainingSet(List<SymbolicPattern> patterns) {
		this.trainingSet = patterns;
	}

	public List<SymbolicPattern> getTrainingSet() {
		return trainingSet;
	}

	public List<SCluster> getSClusters() {
		return clusters;
	}

	public static double trunc(double val) {
		double ret = 0.0;
		try{
			BigDecimal big = new BigDecimal(val + "");
		    ret =  big.setScale(3, BigDecimal.ROUND_DOWN).doubleValue();
		}catch(NumberFormatException e){
			e.printStackTrace();
			//throw new RuntimeException(e);
		}
		return ret;
	}

	public void setOutlierMembershipMatrix(double[] outlierMembershipMatrix) {
		this.outlierMembershipMatrix = outlierMembershipMatrix;
	}

	public double[] getOutlierMembershipMatrix() {
		return outlierMembershipMatrix;
	}
	
	public void setNumAttributes(int n){
		this.numAttributes = n;
	}
	
	public int getIteracoes(){
		return iteracoes;
	}
}
